﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QualityJudgingApp
{
    class AnalysisLogic3
    {
        List<RawDataEntry> myLogs = null;
        double[] coefficientsTime;
        //double[] coefficientsEdit;

        double[] coefficientsCombineTime;
        //double[] coefficientsCombineEdit;

        public double CurrentPosRatio = 0;
        public double CurrentNegRatio = 0;

        public double precision = 0;
        public double recall = 0;
        public double fvalue = 0;
        public double fvalue2 = 0;
        public double accuracy = 0;
        public double optimalY = 0;

        LinearRegression myRegression;

        double interval = 0.01;

        public AnalysisLogic3(List<RawDataEntry> Logs)
        {
            myLogs = Logs;
            myRegression = new LinearRegression();
            coefficientsTime = new double[3];
            //coefficientsEdit = new double[3];
            //distribution = new double[(int)(1.0/interval)];
        }

        public List<int> getAllUserIDs()
        {
            List<int> userIds = new List<int>();
            int lastID = -1;
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                if (myLogs[i].UserID != lastID)
                {
                    lastID = myLogs[i].UserID;
                    userIds.Add(lastID);
                }
            }
            return userIds;
        }

        private double[] GetCoefficients(List<double> xArray1, List<double> xArray2, List<double> yArray)
        {
            double[] ys = new double[yArray.Count];
            double[,] xs = new double[2, yArray.Count];
            for (int i = 0; i < yArray.Count; i++)
            {
                ys[i] = yArray[i];
                xs[0, i] = xArray1[i];
                xs[1, i] = xArray2[i];
            }
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        private double[] GetCoefficients(double[,] xs, double[] ys)
        {
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        private void UpdateCoefCombinations(int XN, int YN, double[,] Xs, List<double> uppers, List<double> lowers, double p, int condition)
        {
            double[] Ys = new double[YN];
            for(int i=0; i<YN;i++)
            {
                Ys[i] = lowers[i]*p+uppers[i]*(1-p);
            }
            if(condition==0)
                coefficientsCombineTime = GetCoefficients(Xs, Ys);
            //else
            //    coefficientsCombineEdit = GetCoefficients(Xs, Ys);
        }

        public int[] EvaluateThres(List<int> chosenIDs, List<int> chosenInternalIDs, bool ifTraining)
        {
            int[] TraingRecognition = new int[4];
            int[] evaluationRecognition = new int[4];
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                //System.Diagnostics.Debug.WriteLine("Processing " + (i / 5 + 1) + "/" + myLogs.Count / 5 + "...");
                RawDataEntry tempLog = myLogs[i];

                double audioLength = tempLog.audioLength;
                double audioQuality = tempLog.audioQuality;
                double picutureQuality = tempLog.pictureQuality;

                double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * audioQuality + coefficientsTime[2] * picutureQuality + coefficientsTime[3];
                // double initialEdits = coefficientsEdit[0] * audioLength + coefficientsEdit[1] * audioQuality + coefficientsEdit[2] * picutureQuality + coefficientsEdit[3];

                //coefficientsTime, coefficientsEdit
                // double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * wordLength + coefficientsTime[2];
                //double initialEdits = coefficientsEdit[0] * audioLength + coefficientsEdit[1] * wordLength + coefficientsEdit[2];

                List<double> times = new List<double>();
                //List<double> edits = new List<double>();
                for (int j = 0; j < 5; j++)
                {
                    if (chosenInternalIDs.Contains(j))//only if chosen
                    {
                        times.Add(myLogs[i + j].timeSpent);
                        //edits.Add(myLogs[i + j].Proof_change_count);
                    }
                }

                times.Sort();
                times.Reverse();
                //edits.Sort();
                //edits.Reverse();

                times.Insert(0, initialTime);
                //edits.Insert(0, initialEdits);

                double thresTime = 0;
                //double thresEdit = 0;
                for (int j = 0; j < times.Count; j++)
                {
                    thresTime += times[j] * coefficientsCombineTime[j];
                    //thresEdit += edits[j] * coefficientsCombineEdit[j];
                }
                thresTime += coefficientsCombineTime[coefficientsCombineTime.Length - 1];
                //thresEdit += coefficientsCombineEdit[coefficientsCombineEdit.Length - 1];

                if (ifTraining&&chosenIDs.Contains(tempLog.UserID))
                {
                    

                    for (int j = 0; j < 5; j++)
                    {
                        if (myLogs[i + j].quality >= 0)//positive
                        {
                            if (myLogs[i + j].timeSpent >= thresTime)
                            {
                                TraingRecognition[0]++;
                            }
                            else
                                TraingRecognition[1]++;
                        }
                        else//negative
                        {
                            if (myLogs[i + j].timeSpent >= thresTime)
                            {
                                TraingRecognition[2]++;
                            }
                            else
                                TraingRecognition[3]++;
                        }
                    }
                }
                else if (!ifTraining && !chosenIDs.Contains(tempLog.UserID))
                {
                    for (int j = 0; j < 5; j++)
                    {
                        if (myLogs[i + j].quality >= 0)//positive
                        {
                            if (myLogs[i + j].timeSpent >= thresTime)
                            {
                                evaluationRecognition[0]++;
                            }
                            else
                                evaluationRecognition[1]++;
                        }
                        else//negative
                        {
                            if (myLogs[i + j].timeSpent >= thresTime)
                            {
                                evaluationRecognition[2]++;
                            }
                            else
                                evaluationRecognition[3]++;
                        }
                    }
                }
            }
            if (ifTraining)
                return TraingRecognition;
            else
                return evaluationRecognition;
        }
        private double GetFitness(int[] modelFits)
        {
            double recognition1 = 0;
            double recall1 = 0;

            recognition1 = (double)modelFits[3] / (modelFits[1] + modelFits[3]);
            recall1 = (double)modelFits[3] / (modelFits[2] + modelFits[3]);

            return recognition1 * recall1 / (recognition1 + recall1) * 2;
            //return ((double)(modelFits[0] + modelFits[3])) / (double)(modelFits[0] + modelFits[1] + modelFits[2] + modelFits[3]);
        }

        private double FindBestP(int XN, int YN,
                                 double[,] XsTime, List<double> rawYTimeUppers, List<double> rawYTimeLowers,
                                 double[,] XsEdit, List<double> rawYEditUppers, List<double> rawYEditLowers,
                                 List<int> chosenIDs, List<int> chosenInternalIDs)
        {
            
            double bestFitRate = 0;
            double BestPercentage = 0;
            for(double p=interval; p<1; p+=interval)
            {
                UpdateCoefCombinations(XN, YN, XsTime, rawYTimeUppers, rawYTimeLowers, p, 0);
                UpdateCoefCombinations(XN, YN, XsEdit, rawYEditUppers, rawYEditLowers, p, 1);
                int[] modelFits = EvaluateThres(chosenIDs, chosenInternalIDs, true);
                double currentFits = GetFitness(modelFits);
                if (currentFits > bestFitRate)
                {
                    bestFitRate = currentFits;
                    BestPercentage = p;
                }
            }
            return BestPercentage;
        }
        private void GetParameters(List<int> chosenIDs, List<int> chosenInternalIDs)//, int condition)//1: use non-negative only; 2: use all
        {
            List<double> audioLengths = new List<double>();
            List<double> audioQuailties = new List<double>();
            List<double> pictureQuailties = new List<double>();

            List<double> workerTimes = new List<double>();
            List<double> workerEdits = new List<double>();

            for (int i = 0; i < myLogs.Count; i += 1)
            {
                RawDataEntry tempLog = myLogs[i];
                if (chosenInternalIDs.Contains(i % 5) && chosenIDs.Contains(tempLog.UserID))
                {
                    //if (condition == 0 && tempLog.Quality < 0)
                    //    continue;
                    //else
                    {
                        audioLengths.Add(tempLog.audioLength);
                        audioQuailties.Add(tempLog.audioQuality);
                        pictureQuailties.Add(tempLog.pictureQuality);

                        workerTimes.Add(tempLog.timeSpent);
                        workerEdits.Add(tempLog.answerLength);
                    }
                }
            }

            //string output = "Total counts: " + audioLengths.Count + "\n";
            coefficientsTime = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerTimes);
            //coefficientsEdit = GetCoefficients(audioLengths, textLength, ProofEdits);

            //slopeTime = GetSlope(audioLengths, textLength);
            //interTime = GetIntercept(audioLengths, workerTimes, slopeTime);
            //output += "Time=" + coefficientsTime[0] + "*Audio+" + coefficientsTime[1] + "*textLength+" +coefficientsTime[2] + "\n";
            //output += "Edit=" + coefficientsEdit[0] + "*Audio+" + coefficientsEdit[1] + "*textLength+" + coefficientsEdit[2] + "\n";
            //return output;
        }

        public double[] GetCoefficients(List<double> xArray1, List<double> xArray2, List<double> xArray3, List<double> yArray)
        {
            double[] ys = new double[yArray.Count];
            double[,] xs = new double[3, yArray.Count];
            for (int i = 0; i < yArray.Count; i++)
            {
                ys[i] = yArray[i];
                xs[0, i] = xArray1[i];
                xs[1, i] = xArray2[i];
                xs[2, i] = xArray3[i];
            }
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        public void Evaluate(List<int> chosenIDs, List<int> chosenInternalIDs)
        {
            GetParameters(chosenIDs, chosenInternalIDs);

            List<List<double>> rawXTimevalues = new List<List<double>>();
            List<List<double>> rawXEditvalues = new List<List<double>>();
            List<double> rawYTimeUppers = new List<double>();
            List<double> rawYTimeLowers = new List<double>();
            List<double> rawYEditUppers = new List<double>();
            List<double> rawYEditLowers = new List<double>();
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                
                RawDataEntry tempLog = myLogs[i];
                if (!chosenIDs.Contains(tempLog.UserID))
                {
                    continue;
                }
                double audioLength = tempLog.audioLength;
                double audioQuality = tempLog.audioQuality;
                double picutureQuality = tempLog.pictureQuality;

                double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * audioQuality + coefficientsTime[2] * picutureQuality + coefficientsTime[3];

                List<double> times = new List<double>();
                //List<double> edits = new List<double>();

                double lowerBoundTime = 0;
                double upperBoundTime = 100000;
                double lowerBoundEdits = 0;
                double upperBoundEdits = 100000;
                for (int j = 0; j < 5; j++)
                {
                    if (chosenInternalIDs.Contains(j))//only if chosen
                    {
                        times.Add(myLogs[i + j].timeSpent);
                        //edits.Add(myLogs[i + j].Proof_change_count);
                        if (myLogs[i + j].quality >= 0)//non-negative
                        {
                            upperBoundTime = (upperBoundTime > myLogs[i + j].timeSpent) ? myLogs[i + j].timeSpent : upperBoundTime;
                            upperBoundEdits = (upperBoundEdits > myLogs[i + j].answerLength) ? myLogs[i + j].answerLength : upperBoundEdits;
                        }
                        else
                        {
                            lowerBoundTime = (lowerBoundTime < myLogs[i + j].timeSpent) ? myLogs[i + j].timeSpent : lowerBoundTime;
                            lowerBoundEdits = (lowerBoundEdits < myLogs[i + j].answerLength) ? myLogs[i + j].answerLength : lowerBoundEdits;
                        }
                    }
                }
                if (upperBoundTime == 100000)
                    upperBoundTime = lowerBoundTime + 1;
                if (upperBoundEdits == 100000)
                    upperBoundEdits = lowerBoundEdits + 1;

                //if (lowerBoundTime == 100000)
                //    lowerBoundTime = upperBoundTime - 1;
                //if (lowerBoundEdits == 100000)
                //    lowerBoundEdits = upperBoundEdits - 1;
                //adjust to make sure lower<upper
                lowerBoundTime = (lowerBoundTime > upperBoundTime) ? upperBoundTime -1: lowerBoundTime;
                lowerBoundEdits = (lowerBoundEdits > upperBoundEdits) ? upperBoundEdits - 1 : lowerBoundEdits;

                times.Sort();
                times.Reverse();

                //edits.Sort();
                //edits.Reverse();

                times.Insert(0, initialTime);
                //edits.Insert(0, initialEdits);
                rawXTimevalues.Add(times);
                //rawXEditvalues.Add(edits);
                rawYTimeUppers.Add(upperBoundTime);
                rawYTimeLowers.Add(lowerBoundTime);
                rawYEditUppers.Add(upperBoundEdits);
                rawYEditLowers.Add(lowerBoundEdits);
            }

            int YN = rawXTimevalues.Count;
            int XN = rawXTimevalues[0].Count;
            double[,] XsTime = new double[XN, YN];
            double[,] XsEdit = new double[XN, YN];
            for(int i=0; i<XN;i++)
            {
                for(int j=0; j<YN; j++)
                {
                    XsTime[i,j] = rawXTimevalues[j][i];
                    //XsEdit[i,j] = rawXEditvalues[j][i];
                }
            }

            double BestTrainingPercentage = FindBestP(XN, YN, XsTime, rawYTimeUppers, rawYTimeLowers,
                                                        XsEdit, rawYEditUppers, rawYEditLowers,
                                                        chosenIDs, chosenInternalIDs);
            optimalY = BestTrainingPercentage;

            UpdateCoefCombinations(XN, YN, XsTime, rawYTimeUppers, rawYTimeLowers, BestTrainingPercentage, 0);
            //UpdateCoefCombinations(XN, YN, XsEdit, rawYEditUppers, rawYEditLowers, BestTrainingPercentage, 1);
            int[] modelFits = EvaluateThres(chosenIDs, chosenInternalIDs, false);
            CurrentPosRatio = (double)modelFits[0] / (modelFits[0] + modelFits[1]);
            CurrentNegRatio = (double)modelFits[3] / (modelFits[3] + modelFits[2]);


            precision = (double)modelFits[0] / (modelFits[0] + modelFits[1]);
            recall = (double)modelFits[0] / (modelFits[0] + modelFits[2]);
            fvalue2 = precision * recall / (precision + recall) * 2;

            precision = (double)modelFits[3] / (modelFits[3] + modelFits[1]);
            recall = (double)modelFits[3] / (modelFits[3] + modelFits[2]);

            //precision = (double)modelFits[0] / (modelFits[0] + modelFits[2]);
            //recall = (double)modelFits[0] / (modelFits[0] + modelFits[1]);
            //accuracy = (double)(modelFits[0] + modelFits[3]) / (modelFits[0] + modelFits[1] + modelFits[2] + modelFits[3]);
            fvalue = precision * recall / (precision + recall) * 2;


            accuracy = (double)(modelFits[0] + modelFits[3]) / (modelFits[0] + modelFits[1] + modelFits[2] + modelFits[3]);

            System.Diagnostics.Debug.WriteLine("\tModel fitness: " + modelFits[0] + "\t" + modelFits[1] + "\t" + modelFits[2] + "\t" + modelFits[3] + "\t" +GetFitness(modelFits)*100+"% \n");
        }

        //one sig change is to include reliable data only for regression test for initial estimation
        public string GetInitialParameters(List<int> chosenIDs, List<int> chosenInternalIDs)//, int condition)//1: use non-negative only; 2: use all
        {
            List<double> audioLengths = new List<double>();
            List<double> audioQuailties = new List<double>();
            List<double> pictureQuailties = new List<double>();

            List<double> workerTimes = new List<double>();
            List<double> workerEdits = new List<double>();

            for (int i = 0; i < myLogs.Count; i += 1)
            {
                RawDataEntry tempLog = myLogs[i];
                if (chosenInternalIDs.Contains(i % 5) && chosenIDs.Contains(tempLog.UserID))
                {
                    if (tempLog.quality < 0)
                        continue;
                    audioLengths.Add(tempLog.audioLength);
                    audioQuailties.Add(tempLog.audioQuality);
                    pictureQuailties.Add(tempLog.pictureQuality);

                    workerTimes.Add(tempLog.timeSpent);
                    workerEdits.Add(tempLog.answerLength);
                }
            }

            string output = "Total counts: " + audioLengths.Count + "\n";
            coefficientsTime = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerTimes);
            //coefficientsEdit = GetCoefficients(audioLengths, textLength, ProofEdits);

            //slopeTime = GetSlope(audioLengths, textLength);
            //interTime = GetIntercept(audioLengths, workerTimes, slopeTime);
            // output += "Time=" + coefficientsTime[0] + "*Audio+" + coefficientsTime[1] + "*textLength+" + coefficientsTime[2] + "\n";
            //output += "Edit=" + coefficientsEdit[0] + "*Audio+" + coefficientsEdit[1] + "*textLength+" + coefficientsEdit[2] + "\n";
            return output;
        }
    }
}
